﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;

namespace CoreHelper.Mapper
{
    /// <summary>
    /// 简单对象映射类，根据相同属性名称复制
    /// </summary>
    public static class SimpleMapper
    {
        public static TOutput Map<TInput, TOutput>(TInput input, TOutput output)
        {
            return Map<TInput, TOutput>(input, output, null);
        }

        public static TOutput Map<TInput, TOutput>(TInput input, TOutput output, Action<TInput, TOutput> fixFun)
        {
            if (input == null) return output;

            try
            {
                var action = GetMapperAction<TInput, TOutput>();
                action(input, output);
            }
            catch
            {
                ;//映射出错自己处理
            }
            if (fixFun != null)
            {
                fixFun(input, output);
            }
            return output;
        }

        public static TOutput Map<TInput, TOutput>(TInput input) where TOutput : new()
        {
            return Map<TInput, TOutput>(input, null);
        }

        public static TOutput Map<TInput, TOutput>(TInput input, Action<TInput, TOutput> fixFun) where TOutput : new()
        {
            if (input == null) return default(TOutput);
            var outputAccessor = SimpleAccessor.GetInfo<TOutput>();
            TOutput output = outputAccessor.NewInstance();
            try
            {
                var action = GetMapperAction<TInput, TOutput>();
                action(input, output);
            }
            catch
            {
                ;//映射出错自己处理
            }
            if (fixFun != null)
            {
                fixFun(input, output);
            }
            return output;
        }

        public static List<TOutput> Map<TInput, TOutput>(IEnumerable<TInput> inputList) where TOutput : new()
        {
            return Map<TInput, TOutput>(inputList, null);
        }
        public static List<TOutput> Map<TInput, TOutput>(IEnumerable<TInput> inputList, Action<TInput, TOutput> fixFun) where TOutput : new()
        {
            List<TOutput> outList = new List<TOutput>();
            if (inputList == null || !inputList.Any())
            {
                return outList;
            }
            var outputAccessor = SimpleAccessor.GetInfo<TOutput>();
            var action = GetMapperAction<TInput, TOutput>();
            foreach (var input in inputList)
            {
                var output = outputAccessor.NewInstance();
                action(input, output);
                if (fixFun != null)
                {
                    fixFun(input, output);
                }
                outList.Add(output);
            }
            return outList;
        }

        static ConcurrentDictionary<string, MulticastDelegate> MapperActionDict = new ConcurrentDictionary<string, MulticastDelegate>();
        public static Action<TInput, TOutput> GetMapperAction<TInput, TOutput>()
        {
            var key = typeof(TInput).FullName + typeof(TOutput).FullName;
            return (Action<TInput, TOutput>)MapperActionDict.GetOrAdd(key, (theKey) =>
            {
                var inputAccessor = SimpleAccessor.GetInfo<TInput>();
                var outputAccessor = SimpleAccessor.GetInfo<TOutput>();

                Action<TInput, TOutput> newValue = null;
                foreach (var kv in inputAccessor.Accessors)
                {
                    if (kv.Value != null)
                    {
                        var accessor = outputAccessor.GetAccessor(kv.Key);
                        if (accessor != null && accessor.Prop != null)
                        {
                            if (accessor.Prop.PropertyType == kv.Value.Prop.PropertyType || accessor.Prop.PropertyType.IsAssignableFrom(kv.Value.Prop.PropertyType))
                            {
                                newValue += new Action<TInput, TOutput>((input, output) =>
                                {
                                    accessor.Set(output, kv.Value.Get(input));
                                });
                            }
                            else if (accessor.Prop.PropertyType.IsEnum && accessor.Prop.PropertyType.GetEnumUnderlyingType() == kv.Value.Prop.PropertyType)
                            {
                                newValue += new Action<TInput, TOutput>((input, output) =>
                                {
                                    accessor.Set(output, kv.Value.Get(input));
                                });
                            }
                            else if (kv.Value.Prop.PropertyType.IsEnum && kv.Value.Prop.PropertyType.GetEnumUnderlyingType() == accessor.Prop.PropertyType)
                            {
                                newValue += new Action<TInput, TOutput>((input, output) =>
                                {
                                    accessor.Set(output, kv.Value.Get(input));
                                });
                            }
                            else if (kv.Value.Prop.PropertyType.Name == "Nullable`1" && kv.Value.Prop.PropertyType.GetGenericArguments()[0] == accessor.Prop.PropertyType)
                            {
                                newValue += new Action<TInput, TOutput>((input, output) =>
                                {
                                    var val = kv.Value.Get(input);
                                    if (val != null)
                                    {
                                        accessor.Set(output, val);
                                    }
                                });
                            }
                            else if (accessor.Prop.PropertyType == PrimitiveTypes.String)
                            {
                                newValue += new Action<TInput, TOutput>((input, output) =>
                                {
                                    var val = kv.Value.Get(input);
                                    if (val != null)
                                    {
                                        accessor.Set(output, val.ToString());
                                    }
                                });
                            }
                            else if (kv.Value.Prop.PropertyType == PrimitiveTypes.String)
                            {
                                newValue += new Action<TInput, TOutput>((input, output) =>
                                {
                                    var val = kv.Value.Get(input);
                                    if (val != null)
                                    {
                                        accessor.Set(output, Convert.ChangeType(val, accessor.Prop.PropertyType));
                                    }
                                });
                            }
                        }
                    }
                }
                return newValue;
            });
        }
    }
}
